【コピペで使える】リソース削除忘れを防止する自動削除 CloudFormation を作ってみた

【コピペで使える】リソース削除忘れを防止する自動削除 CloudFormation を作ってみた

Clock Icon2023.01.29

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

こんにちは、森田です。

みなさんは、AWSリソースちゃんと削除できていますか?

私は、特に CloudFormation で構築した場合に、忘れてしまうことがあります。

本記事では、リソース削除忘れを防止する方法として、自動で CloudFormation スタックを削除する方法をご紹介します。

アーキテクチャ

以前、以下のような 時限式CloudFormation をご紹介しましたが、それよりシンプルな構成となっております。

CloudFormationテンプレートの内部に EventBridge Scheduler を埋め込み、特定の時間になったらスタックの削除を行います。

事前準備

なお、事前準備として EventBridge Scheduler で利用する IAMロール を CloudFormation で用意する必要があります。

Description: EventBridge Scheduler IAM Role
Resources:
  Role:
    Type: 'AWS::IAM::Role'
    Properties:
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          -
            Effect: "Allow"
            Principal:
              Service:
                - scheduler.amazonaws.com
            Action:
              - "sts:AssumeRole"
      ManagedPolicyArns:
        - 'arn:aws:iam::aws:policy/PowerUserAccess'
      Path: "/"

Outputs:
  DeleteSchedulerRoleARN:
    Value: !GetAtt Role.Arn
    Export:
      Name: DeleteSchedulerRoleARN

 

コピペして使えるテンプレート

あとは、お好きなCloudFormationテンプレートに以下のパラメータとリソースを追加するだけで自動で削除してくれます。

Parameters:
  Hour:
    Description: Please type Delete Hour.
    Type: String
    Default: "13"
  Minute:
    Description: Please type Delete Minute.
    Type: String
    Default: "00"
  
Resources:
  EventSchedule:
    Type: AWS::Scheduler::Schedule
    Properties:
      Description: 'Delete Schedule'
      ScheduleExpression: !Sub "cron(${Minute} ${Hour} ? * * *)"
      ScheduleExpressionTimezone: "Asia/Tokyo"
      FlexibleTimeWindow:
        Mode: 'OFF'
      State: ENABLED
      Target:
        Arn: "arn:aws:scheduler:::aws-sdk:cloudformation:deleteStack"
        Input: !Sub "{ \"StackName\": \"${AWS::StackName}\" }"
        RoleArn: !ImportValue DeleteSchedulerRoleARN

パラメータについては、削除する時間の HourMinute を入力します。

  • Hour
    • 削除する時間の時
  • Minute
    • 削除する時間の分

実際にやってみる

以下の記事のテンプレートに対して実際に適用させてみます。

 

なお、EventBridge Scheduler で利用する IAMロールは作成済みとします。

作成していない方は、事前準備を実施してください。

テンプレートの準備

以下のようにテンプレートを用意します。元のテンプレートに削除用のテンプレートを追加します。

AWSTemplateFormatVersion: '2010-09-09'
Description: ALB and Nginx on EC2
Parameters:
  Hour:
    Description: Please type Delete Hour.
    Type: String
    Default: "13"
  Minute:
    Description: Please type Delete Minute.
    Type: String
    Default: "00"
  
Resources:
  EventSchedule:
    Type: AWS::Scheduler::Schedule
    Properties:
      Description: 'Delete Schedule'
      ScheduleExpression: !Sub "cron(${Minute} ${Hour} ? * * *)"
      ScheduleExpressionTimezone: "Asia/Tokyo"
      FlexibleTimeWindow:
        Mode: 'OFF'
      State: ENABLED
      Target:
        Arn: "arn:aws:scheduler:::aws-sdk:cloudformation:deleteStack"
        Input: !Sub "{ \"StackName\": \"${AWS::StackName}\" }"
        RoleArn: !ImportValue DeleteSchedulerRoleARN
  VPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: 10.0.0.0/16
      Tags:
      - Key: Name
        Value: !Sub ${AWS::StackName}-vpc

  PublicSubnet0:
    Type: AWS::EC2::Subnet
    DependsOn: AttachGateway
    Properties:
      VpcId: !Ref VPC
      AvailabilityZone: ap-northeast-1a
      CidrBlock: 10.0.0.0/24
      Tags:
      - Key: Name
        Value: !Sub ${AWS::StackName}-public-1a-subnet
  PublicSubnet1:
    Type: AWS::EC2::Subnet
    DependsOn: AttachGateway
    Properties:
      VpcId: !Ref VPC
      AvailabilityZone: ap-northeast-1c
      CidrBlock: 10.0.1.0/24
      Tags:
      - Key: Name
        Value: !Sub ${AWS::StackName}-public-1c-subnet

  InternetGateway:
    Type: AWS::EC2::InternetGateway
    Properties:
      Tags:
      - Key: Name
        Value: !Sub ${AWS::StackName}-igw
  AttachGateway:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      VpcId: !Ref VPC
      InternetGatewayId: !Ref InternetGateway

  PublicRouteTable:
    Type: AWS::EC2::RouteTable
    DependsOn: AttachGateway
    Properties:
      VpcId: !Ref VPC
      Tags:
      - Key: Name
        Value: !Sub ${AWS::StackName}-public-rt
  PublicRoute:
    Type: AWS::EC2::Route
    DependsOn: AttachGateway
    Properties:
      RouteTableId: !Ref PublicRouteTable
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref InternetGateway
  
  PublicSubnet0RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PublicSubnet0
      RouteTableId: !Ref PublicRouteTable
  PublicSubnet1RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PublicSubnet1
      RouteTableId: !Ref PublicRouteTable

  ALBSecurityGroup:
    Type: "AWS::EC2::SecurityGroup"
    Properties:
        GroupDescription: "ALB SG"
        GroupName: !Sub ${AWS::StackName}-alb-sg
        VpcId: !Ref VPC
        Tags:
          - Key: Name
            Value: !Sub ${AWS::StackName}-alb-sg
        SecurityGroupIngress:
          - IpProtocol: tcp
            FromPort: 80
            ToPort: 80
            CidrIp: 0.0.0.0/0
          - IpProtocol: tcp
            FromPort: 443
            ToPort: 443
            CidrIp: 0.0.0.0/0

  EC2SecurityGroup:
    Type: "AWS::EC2::SecurityGroup"
    Properties:
        GroupDescription: "EC2 SG"
        GroupName: !Sub ${AWS::StackName}-ec2-sg
        VpcId: !Ref VPC
        Tags:
          - Key: Name
            Value: !Sub ${AWS::StackName}-ec2-sg
        SecurityGroupIngress:
          - IpProtocol: tcp
            FromPort: 80
            ToPort: 80
            SourceSecurityGroupId: !Ref ALBSecurityGroup

  EC2InstanceProfile: 
    Type: AWS::IAM::InstanceProfile
    Properties: 
      Path: "/"
      Roles: 
        - !Ref EC2Role
      InstanceProfileName: !Sub ${AWS::StackName}-ec2-profile
  EC2Role:
    Type: "AWS::IAM::Role"
    Properties:
        Path: "/"
        RoleName: !Sub ${AWS::StackName}-ec2-role
        Tags: 
        - Key: Name
          Value: !Sub ${AWS::StackName}-ec2-role
        AssumeRolePolicyDocument: 
          Version: 2012-10-17
          Statement:
          - Effect: Allow
            Principal:
              Service: ec2.amazonaws.com
            Action: sts:AssumeRole
        ManagedPolicyArns: 
          - "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore"
  KeyPair:
    Type: 'AWS::EC2::KeyPair'
    Properties:
      KeyName: !Sub ${AWS::StackName}-keypair

  WebServer:
    Type: AWS::EC2::Instance
    Properties:
      Tags:
        - Key: Name
          Value: !Sub ${AWS::StackName}-web
      InstanceType: t3.nano
      BlockDeviceMappings:
        - DeviceName: /dev/sda1
          Ebs:
            VolumeType: gp3
            VolumeSize: 8
            DeleteOnTermination: true
            Encrypted: true
      NetworkInterfaces: 
        - AssociatePublicIpAddress: "true"
          DeviceIndex: "0"
          GroupSet: 
            - !Ref EC2SecurityGroup
          SubnetId: !Ref PublicSubnet0
      ImageId: '{{resolve:ssm:/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2}}'
      IamInstanceProfile: !Ref EC2InstanceProfile
      KeyName: !Ref KeyPair
      DisableApiTermination: false
      EbsOptimized: true
      UserData: 
        Fn::Base64: |
          #!/bin/bash
          sudo amazon-linux-extras install nginx1
          sudo systemctl enable nginx
          sudo systemctl start nginx

  ALB:
    Type: AWS::ElasticLoadBalancingV2::LoadBalancer
    Properties:
      Type: "application"
      Scheme: "internet-facing"
      Name: !Sub ${AWS::StackName}-alb
      Tags:
        - Key: Name
          Value: !Sub ${AWS::StackName}-alb
      IpAddressType: ipv4
      Subnets: 
        - !Ref PublicSubnet0
        - !Ref PublicSubnet1
      SecurityGroups: 
        - !Ref ALBSecurityGroup

  ListenerHTTP:
    Type: AWS::ElasticLoadBalancingV2::Listener
    Properties:
      DefaultActions:
        - Type: forward
          TargetGroupArn: !Ref TargetGroup
      LoadBalancerArn: !Ref ALB
      Port: 80
      Protocol: HTTP
  
  TargetGroup:
    Type: AWS::ElasticLoadBalancingV2::TargetGroup
    Properties:
      Name: !Sub ${AWS::StackName}-tg
      Tags:
        - Key: Name
          Value: !Sub ${AWS::StackName}-tg
      Port: 80
      Protocol: HTTP
      Matcher:
        HttpCode: '200'
      VpcId: !Ref VPC
      TargetType: instance
      Targets:
        - Id: !Ref WebServer

Outputs:
  ALBURL:
    Description: ALB endpoint URL
    Value: !Join
        - ""
        - - http://
          - !GetAtt ALB.DNSName

スタックの作成

あとは、上記のテンプレートでスタックを作成します。以下の場合は、14:00に削除するようにパラメータを与えています。

スタック作成後は、しばらく待つと通常通り CREATE_COMPLETE になります。

スタックの自動削除

パラメータに指定した時間までしばらく待ちます。

すると、以下のようにスタックが自動的に削除されます。

 

まとめ

本記事では、自動でCloudFormationのスタックを削除する方法をご紹介しました。

EventBridge Scheduler のおかげで結構シンプルに実現できたと思います。

皆さんもリソースの削除忘れがないようこのテンプレートをぜひ利用してみてください。

 

Share this article

facebook logohatena logotwitter logo

© Classmethod, Inc. All rights reserved.